package com.hu.core.ioc;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.hu.core.ioc.bean.BeanConfig;
import com.hu.core.ioc.bean.BeanProperty;
import com.hu.core.mvc.DispatcherServlet;
import com.hu.core.util.StringUtil;
import com.hu.example.proxy.ConnectionDynamicProxy;


public class BeanFactory {
    private static BeanFactory beanFactory;
    private static Map<String, BeanConfig> beans = new HashMap<String, BeanConfig>();
    private static Map<String, Object> objects = new HashMap<String, Object>();

    private BeanFactory() {
    }

    public static BeanFactory getInstance() {
        if (null == beanFactory) {
            beanFactory = new BeanFactory();
            beanFactory.init();
        }

        return beanFactory;
    }

    public void init() {
        InputStream inputStream = null;
        try {
            inputStream = DispatcherServlet.class.getClassLoader().getResourceAsStream(BeanDefault.DEFAULT_SETTING_PATH);
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(inputStream);
            Element element = document.getDocumentElement();

            NodeList beanNodes = element.getElementsByTagName("bean");
            if(beanNodes == null) {
                return;
            }
            int beanLength = beanNodes.getLength();
            for(int i = 0; i < beanLength; i++) {
                Element beanElement = (Element) beanNodes.item(i);
                BeanConfig bean = new BeanConfig();
                String id = beanElement.getAttribute("id");
                bean.setId(id);
                String className = beanElement.getAttribute("class");
                bean.setCls(className);
                String scope = beanElement.getAttribute("scope");
                bean.setScope(scope);
                beans.put(id, bean);
                NodeList beanPropertyNodes = beanElement.getElementsByTagName("property");

                if(beanPropertyNodes != null) {
                    int beanPropertyLength = beanPropertyNodes.getLength();
                    for(int j = 0; j < beanPropertyLength; j++) {
                        Element beanPropertyElement = (Element) beanPropertyNodes.item(j);
                        BeanProperty beanProperty = new BeanProperty();
                        beanProperty.setName(beanPropertyElement.getAttribute("name"));
                        beanProperty.setRef(beanPropertyElement.getAttribute("ref"));
                        beanProperty.setValue(beanPropertyElement.getAttribute("value"));
                        bean.addProperty(beanProperty);
                    }
                }
            }
        }  catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Object getBean(String id) {
        if (beans.containsKey(id)) {
            System.out.println("getBean -> " + id);
            BeanConfig bean = beans.get(id);
            String scope = bean.getScope();
            if(StringUtil.isEmpty(scope)) {
                scope = "singleton";
            }

            if ("singleton".equalsIgnoreCase(scope)) {
                if (objects.containsKey(id)) {
                    return objects.get(id);
                }
            }

            String className = bean.getCls();
            Class<?> clz = null;
            try {
                System.out.println("Ins -> " + className);
                clz = Class.forName(className);
                Object object = clz.newInstance();

                if ("singleton".equalsIgnoreCase(scope)) {
                    objects.put(id, object);
                }

                List<BeanProperty> beanProperies = bean.getProperties();

                if (beanProperies != null && !beanProperies.isEmpty()) {
                    for (BeanProperty beanProperty : beanProperies) {
                        String propertyName = beanProperty.getName();
                        String firstChar = propertyName.substring(0, 1);
                        String leaveChar = propertyName.substring(1);
                        String methodName = firstChar.toUpperCase() + leaveChar;

                        Method method = null;
                        Method[] methods = clz.getMethods();
                        System.out.println("find set -> set" + methodName);
                        for (Method methodInClass : methods) {
                            String methodNameInClass = methodInClass.getName();
                            if (methodNameInClass.equals("set" + methodName)) {
                                method = methodInClass;
                                break;
                            }
                        }

                        String ref = beanProperty.getRef();
                        String value = beanProperty.getValue();
                        if (!StringUtil.isEmpty(ref)) {
                            System.out.println("ref -> " + ref);
                            Object refObject = this.getBean(ref);
                            System.out.println("invoke -> " + method + "-" + object + "-" + refObject);
                            method.invoke(object, refObject);
                        } else if (!StringUtil.isEmpty(value)) {
                            Class<?>[] parmts = method.getParameterTypes();
                            String propertyValue = beanProperty.getValue();
                            if (parmts[0] == String.class) {
                                method.invoke(object, propertyValue);
                            }

                            if (parmts[0] == int.class) {
                                method.invoke(object, Integer.parseInt(propertyValue));
                            }

                            if (parmts[0] == boolean.class) {
                                method.invoke(object, Boolean.parseBoolean(propertyValue));
                            }
                        }
                    }
                }

                if (object.getClass().getPackage().getName().equals(BeanDefault.PROXY_PACKAGE)) {
                    ConnectionDynamicProxy connectionDynamicProxy = new ConnectionDynamicProxy();
                    connectionDynamicProxy.setTarget(object);
                    Object proxyObject = Proxy.newProxyInstance(object.getClass().getClassLoader(),
                            object.getClass().getInterfaces(), connectionDynamicProxy);

                    return proxyObject;
                }

                return object;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return null;
    }
}
